package com.kdgc.energy.dmm.time;

import com.kdgc.energy.dmm.Environment;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 耗时分析器
 *
 * @author xu.wenchang
 * @version 1.0 2022/04/21
 */
public class TimeAnalyser {
    public static final TimeAnalyser INST = new TimeAnalyser();
    private static final TimeStatistical POISON_PILL = new TimeStatistical() {
        @Override
        public String getName() {
            return "poison pill";
        }
    };
    private BlockingQueue<TimeStatistical> queue;
    private ScheduledExecutorService ses;
    private ExecutorService es;
    private volatile boolean reject;
    private volatile AnalysePeriod current;
    
    private TimeAnalyser() {
        this.queue = new LinkedBlockingQueue<>();
        this.ses = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "TimeAnalyserScheduleThread"));
        this.es = Executors.newSingleThreadExecutor(r -> new Thread(r, "TimeAnalyserConsumeThread"));
        this.current = new AnalysePeriod(System.currentTimeMillis());
        this.start();
    }
    
    private void start() {
        this.consume();
        this.schedule();
    }
    
    public void close() {
        this.addExecuteRecord(POISON_PILL);
        this.reject = true;
        this.es.shutdown();
        this.ses.shutdown();
        this.queue.clear();
        this.current = null;
    }
    
    private void schedule() {
        this.ses.scheduleWithFixedDelay(() -> {
            PeriodInfo periodInfo = current.periodInfo();
            Environment.getWriter()
                       .write(periodInfo);
            current.close();
            
            current = new AnalysePeriod(System.currentTimeMillis());
        }, 1L, 1L, TimeUnit.HOURS);
    }
    
    private void consume() {
        this.es.execute(() -> {
            while (!Thread.currentThread()
                          .isInterrupted()) {
                try {
                    TimeStatistical ts = this.queue.take();
                    
                    if (ts == POISON_PILL) {
                        break;
                    }
                    
                    current.add(ts);
                } catch (InterruptedException e) {
                    Environment.getWriter()
                               .write(e);
                    TimeAnalyser.this.close();
                    Environment.getWriter()
                               .write("reject time statistical now.");
                    Thread.currentThread()
                          .interrupt();
                }
            }
        });
    }
    
    /**
     * 添加执行记录
     */
    public void addExecuteRecord(TimeStatistical ts) {
        if (!reject) {
            this.queue.add(ts);
        }
    }
    
    /**
     * 分析时段
     */
    static class AnalysePeriod {
        private static final long GROUP_TIME_PERIOD = TimeUnit.MINUTES.toMillis(1L);
        private static final long SLOW = 3000L;
        private static final AtomicInteger COUNTER = new AtomicInteger(1);
        int groupId;
        long startTime;
        long endTime;
        List<TimeStatistical> list;
        
        AnalysePeriod(long startTime) {
            this.groupId = COUNTER.getAndIncrement();
            this.startTime = startTime;
            this.endTime = startTime + GROUP_TIME_PERIOD;
            this.list = new ArrayList<>();
        }
        
        void add(TimeStatistical ts) {
            this.list.add(ts);
        }
        
        PeriodInfo periodInfo() {
            Map<String, Double> map = list.stream()
                                          .collect(Collectors.groupingBy(TimeStatistical::getName, Collectors.averagingLong(TimeStatistical::total)));
            map = map.entrySet()
                     .stream()
                     .filter(entry -> entry.getValue()
                                           .longValue() > SLOW)
                     .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
            return new PeriodInfo(groupId, startTime, endTime, list.size(), map);
        }
        
        void close() {
            this.list.clear();
            this.list = null;
        }
    }
    
    /**
     * 时段信息
     */
    static class PeriodInfo {
        static final SimpleDateFormat SDF = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
        int groupId;
        long startTime;
        long endTime;
        int num;
        Map<String, Double> slowMap;
        
        PeriodInfo(int groupId, long startTime, long endTime, int num, Map<String, Double> slowMap) {
            this.groupId = groupId;
            this.startTime = startTime;
            this.endTime = endTime;
            this.num = num;
            this.slowMap = slowMap;
        }
        
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-");
            builder.append(System.lineSeparator());
            Date start = new Date(this.startTime);
            Date end = new Date(this.endTime);
            builder.append("时段[ ");
            builder.append(this.groupId);
            builder.append(" ] ");
            builder.append(SDF.format(start));
            builder.append(" - ");
            builder.append(SDF.format(end));
            builder.append(System.lineSeparator());
            
            builder.append("执行次数: ");
            builder.append(num);
            builder.append(System.lineSeparator());
            builder.append("slow sql: ");
            
            if (this.slowMap.size() > 0) {
                for (Map.Entry<String, Double> entry : this.slowMap.entrySet()) {
                    builder.append(entry.getKey());
                    builder.append(" 平均耗时: ");
                    builder.append(entry.getValue()
                                        .longValue());
                    builder.append(" 毫秒");
                    builder.append(System.lineSeparator());
                }
            } else {
                builder.append("无");
                builder.append(System.lineSeparator());
            }
            
            builder.append("-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-");
            return builder.toString();
        }
    }
}
